-
Notifications
You must be signed in to change notification settings - Fork 85
[Server] Add ToolExecutionExceptionInterface for custom tool error handling #73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
Hi @MartkCz, The Currently the I would prefer, instead of creating a new interface to implement, you create a |
|
@chr-hertel Thanks for your time.
interface ReferenceExceptionInterface {}
final class ExecutionReferenceException extends \RuntimException implements ReferenceExceptionInterface {} // business errors
abstract class ProtocolReferenceException extends \LogicException implements ReferenceExceptionInterface {} // unknown tools, invalid arguments, server errors
final class ToolNotFoundReferenceException extends ProtocolReferenceException {}
final class InvalidParamsReferenceException extends ProtocolReferenceException {}
final class InternalErrorReferenceException extends ProtocolReferenceException {}This is just a proposal — there wouldn’t necessarily have to be so many exception classes inheriting from ProtocolReferenceException, but I wanted to preserve throwing ToolNotFoundException. The naming is also not final. and then this is simplified from this: try {
$result = $this->referenceHandler->handle($toolReference, $arguments);
/** @var TextContent[]|ImageContent[]|EmbeddedResource[]|AudioContent[] $formattedResult */
$formattedResult = $toolReference->formatResult($result);
$this->logger->debug('Tool executed successfully', [
'name' => $toolName,
'result_type' => \gettype($result),
]);
return new CallToolResult($formattedResult);
} catch (RegistryException $e) {
throw new ToolCallException($request, $e);
} catch (\Throwable $e) {
if ($e instanceof ToolCallException) {
throw $e;
}
$this->logger->error('Tool execution failed', [
'name' => $toolName,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw new ToolCallException($request, RegistryException::internalError('Error while executing tool', $e));
}to this: try {
$result = $this->referenceHandler->handle($toolReference, $arguments);
/** @var TextContent[]|ImageContent[]|EmbeddedResource[]|AudioContent[] $formattedResult */
$formattedResult = $toolReference->formatResult($result);
$this->logger->debug('Tool executed successfully', [
'name' => $toolName,
'result_type' => \gettype($result),
]);
return new CallToolResult($formattedResult);
} catch (ReferenceExceptionInterface $e) {
throw $e;
} catch (\Throwable $e) {
$this->logger->error('Tool execution failed', [
'name' => $toolName,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
throw new InternalErrorReferenceException($request, $e);
}and this: try {
$content = $this->toolCaller->call($message);
} catch (ToolNotFoundException $exception) {
return Error::forInvalidParams($exception->getMessage(), $message->getId());
} catch (ToolCallException $exception) {
$registryException = $exception->registryException;
if ($registryException instanceof ReferenceExecutionException) {
return new Response($message->getId(), CallToolResult::error(array_map(
fn (string $message): TextContent => new TextContent($message),
$registryException->messages,
)));
}
return new Error($message->getId(), $registryException->getCode(), $registryException->getMessage());
}to this: try {
$content = $this->toolCaller->call($message);
} catch (ProtocolReferenceException $exception) {
return new Error($message->getId(), $exception->getCode(), $exception->getMessage());
} catch (ExecutionReferenceException $exception) {
return new Response($message->getId(), CallToolResult::error(array_map(
fn (string $message): TextContent => new TextContent($message),
$exception->messages,
)));
} |
|
@MartkCz Thanks for the follow-up and the detailed explanation! 🙏 btw, this needs a rebase - and please make sure to have a squashed, signed commit please |
|
Hi @chr-hertel , sorry, but I didn't have time to finish the PR. At a quick glance it looks like a sufficient solution, the only question is whether it's really necessary to log errors from ToolCallException. In my experience, they were never needed, and in the worst case you can log directly in the McpTool method. If I take an example where the AI/human calls divideNumbers with $b as 0 and McpTool responds that you can't divide by zero, I would expect it to handle that and provide a different number or ask for a different one. Otherwise, I would end up with Sentry full of logs that I can’t and don’t want to deal with, because it’s not an application error. Logging is probably fine during debugging, but I would keep it at most at debug/info severity. Thanks for mention. |
|
Thanks @MartkCz for the fast feedack - referring to this one, right? php-sdk/src/Server/Handler/Request/CallToolHandler.php Lines 77 to 85 in 7a3b473
|
Motivation and Context
The current tool execution system lacks a standardized way for handling tool-specific errors. This change provides a consistent way for tools to report errors.
How Has This Been Tested?
Added unit tests in ToolCallerTest.php
Breaking Changes
No breaking changes.
Types of changes
Checklist
Additional context
The implementation includes modifications to:
Usage